`include "defines.svh"

module StageEX (
    input   logic                           clock,
    input   logic                           reset,

    // StageEX -> StageMA
    output  logic                           ex2ma_valid,
    output  logic [`EX2MA_BUS_WD-1:0]       ex2ma_bus,

    // StageMA -> StageEX
    input   logic                           ma2ex_allow,

    // StageEX -> StageID
    output  logic                           ex2id_allow,

    // StageID -> StageEX
    input   logic                           id2ex_valid,
    input   logic [`ID2EX_BUS_WD-1:0]       id2ex_bus,

    // CSR -> StageEX
    input   logic                           csr_taken,

    // StageEX -> StageID @ Forward & Block
    output  logic [`EX_RF_HZD_BUS_WD-1:0]   ex_rf_hzd_bus,
    output  logic [`EX_CSR_BLK_BUS_WD-1:0]  ex_csr_blk_bus,

    // StageMA -> StageEX @ Cancel Flag
    input   logic                           ma_cancel,

    // StageWB -> StageEX @ Cancel Flag
    input   logic                           wb_cancel,
    // StageEX -> StageIF @ Cancel Flag
    output  logic                           ex_cancel,

    /* ********* DataBus Interface ********* */
    output  logic                           dbus_req,
    output  logic                           dbus_op,
    output  logic [2:0]                     dbus_size,
    output  logic [7:0]                     dbus_mask,
    output  logic [31:0]                    dbus_addr,
    output  logic [63:0]                    dbus_wdata,
    input   logic                           dbus_addr_ok,
    output  logic                           dbus_uncache,
    input   logic                           dbus_cache_miss
    /* ********* DataBus Interface ********* */
);

    logic           ex_valid;
    logic           ex_ready;

/* ************************* Pipeline ************************* */
/* -------------- StageID <=> StageEX -------------- */
    // for debug signal
    logic           icache_miss;
    // to StageEX signal
    logic [63:0]    ex_pc;
    logic [31:0]    ex_inst;
    logic [63:0]    ex_next_pc;
    /* verilator lint_off UNUSEDSIGNAL */
    logic [15:0]    id2ex_excp_flags;
    logic [15:0]    id2ex_intr_flags;
    logic [11:0]    alu_op;
    logic           alu_word;
    logic           alu_unsign;
    logic [1:0]     alu_mul_signs;
    logic           alu_mul_high;
    logic [63:0]    alu_src1;
    logic [63:0]    alu_src2;
    // to StageMA signal
    logic           mem_en;
    logic           mem_we;
    logic [3:0]     mem_op;
    logic [31:0]    mem_addr;
    logic [2:0]     mem_size;
    logic [63:0]    mem_wdata;
    logic           load_sext;
    logic [2:0]     rf_sel;
    logic [63:0]    csr_rdata;
    // to Regfile signal
    logic           ex_rf_we;
    logic [4:0]     ex_rf_waddr;
    // to CSR signal
    logic           inst_mret;
    logic           ex_csr_we;
    logic [11:0]    ex_csr_waddr;
    logic [63:0]    ex_csr_wdata;

    logic [`ID2EX_BUS_WD-1:0]   id2ex_bus_R;

    assign {
        // for debug
        icache_miss,
        // to StageEX
        ex_pc,
        ex_inst,
        ex_next_pc,
        id2ex_excp_flags,
        id2ex_intr_flags,
        alu_op,
        alu_word,
        alu_unsign,
        alu_mul_signs,
        alu_mul_high,
        alu_src1,
        alu_src2,
        mem_en,
        mem_we,
        mem_op,
        mem_size,
        mem_wdata,
        // to StageMA
        load_sext,
        rf_sel,
        csr_rdata,
        // to Regfile
        ex_rf_we,
        ex_rf_waddr,
        // to CSR
        inst_mret,
        ex_csr_we,
        ex_csr_waddr,
        ex_csr_wdata
    } = id2ex_bus_R;

    assign  ex_ready = alu_done & (~dbus_req | dbus_req & dbus_addr_ok);
    assign  ex2id_allow = (~ex_valid) | (ex_ready & ma2ex_allow);
    assign  ex2ma_valid = ex_valid & ex_ready;

    always_ff @ (posedge clock) begin
        if (reset | csr_taken) begin
            ex_valid <= 1'b0;
        end
        else if (ex2id_allow) begin
            ex_valid <= id2ex_valid;
        end
    end

    always_ff @ (posedge clock) begin
        if (reset) begin
            id2ex_bus_R <= `ID2EX_BUS_WD'b0;
        end
        else if (id2ex_valid & ex2id_allow) begin
            id2ex_bus_R <= id2ex_bus;
        end
    end
/* -------------- StageID <=> StageEX -------------- */

/* -------------- StageEX <=> StageMA -------------- */
    wire inst_mem       = mem_en;
    wire dcache_miss    = dbus_cache_miss;
    wire mem_has_req    = dbus_req;

    assign ex2ma_bus = {
        // for debug
        inst_mem,
        skip_ref,
        icache_miss,
        dcache_miss,
        // to StageMA
        ex_pc,
        ex_inst,
        ex_next_pc,
        ex_excp_flags,
        ex_intr_flags,
        mem_op,
        mem_addr,
        load_sext,
        mem_has_req,
        rf_sel,
        alu_result,
        csr_rdata,
        // to Regfile
        ex_rf_we,
        ex_rf_waddr,
        // to CSR
        inst_mret,
        ex_csr_we,
        ex_csr_waddr,
        ex_csr_wdata
    };
    /* -------------- StageEX <=> StageMA -------------- */
/* ************************* Pipeline ************************* */


/* ************************* ALU ************************* */
    logic           alu_done;
    logic [63:0]    alu_result;
    ALU alu (
        .clock      (clock),
        .reset      (reset),
        .flush      (csr_taken),
        .valid      (ex_valid),
        .operation  (alu_op),
        .src1       (alu_src1),
        .src2       (alu_src2),
        .word       (alu_word),
        .unsign     (alu_unsign),
        .mul_signs  (alu_mul_signs),
        .mul_high   (alu_mul_high),
        .result     (alu_result),
        .done       (alu_done)
    );
/* ************************* ALU ************************* */


/* ******************************** RegFile ******************************** */
    // RegFile Forward & Block
    wire        is_read_mem = (mem_en & ~mem_we);
    wire        ex_rf_blk_flag = ex_valid & ex_rf_we & (|ex_rf_waddr) &  (is_read_mem | ~ex_ready);
    wire        ex_rf_fwd_flag = ex_valid & ex_rf_we & (|ex_rf_waddr) & ~(is_read_mem | ~ex_ready);
    wire [63:0] ex_rf_fwd_data = alu_result;
    assign ex_rf_hzd_bus = {
        ex_rf_blk_flag,
        ex_rf_fwd_flag,
        ex_rf_waddr,
        ex_rf_fwd_data
    };
/* ******************************** RegFile ******************************** */


/* ************************* CSR ************************* */
    // CSR Block
    wire ex_csr_blk_flag = ex_valid & ex_csr_we;
    assign ex_csr_blk_bus = {
        ex_csr_blk_flag,
        ex_csr_waddr
    };
/* ************************* CSR ************************* */


/* ************************************* Trap ************************************* */
    wire has_other_excp = (|id2ex_excp_flags[3:0]) | (id2ex_excp_flags[5]) | (|id2ex_excp_flags[15:7]);
    wire addr_misaligned = (sh_op & (mem_addr[0:0] != 0))
                         | (sw_op & (mem_addr[1:0] != 0))
                         | (sd_op & (mem_addr[2:0] != 0));
    logic [15:0] ex_excp_flags;
    assign ex_excp_flags[3:0]= id2ex_excp_flags[3:0];
    assign ex_excp_flags[4] = ex_valid & (~has_other_excp) & mem_en & (~mem_we) & addr_misaligned;
    assign ex_excp_flags[5] = id2ex_excp_flags[5];
    assign ex_excp_flags[6] = ex_valid & (~has_other_excp) & mem_en & ( mem_we) & addr_misaligned;
    assign ex_excp_flags[15:7]= id2ex_excp_flags[15:7];
    wire [15:0] ex_intr_flags = id2ex_intr_flags;

/* ----------------------------- Cancel Flag ----------------------------- */
    assign ex_cancel = ex_valid & ((|ex_excp_flags) | (|ex_intr_flags) | inst_mret);

    wire ex_has_intr = ex_valid & (|ex_intr_flags);
    wire ex_cancel_no_intr = ex_cancel & (~ex_has_intr);
    wire cancel_memory = ex_cancel_no_intr // 不要加入 ex_has_intr !!!!!!!
                       | ma_cancel
                       | wb_cancel;
/* ----------------------------- Cancel Flag ----------------------------- */
/* ************************************* Trap ************************************* */


/* ************************* MEM ************************* */
    assign mem_addr = alu_result[31:0];

    wire sb_op  = mem_op[0];
    wire sh_op  = mem_op[1];
    wire sw_op  = mem_op[2];
    wire sd_op  = mem_op[3];

    localparam [7:0] LUT_SB [8] = '{
        8'b00000001, 8'b00000010, 8'b00000100, 8'b00001000,
        8'b00010000, 8'b00100000, 8'b01000000, 8'b10000000
    };
    localparam [7:0] LUT_SH [4] = '{
        8'b00000011, 8'b00001100, 8'b00110000, 8'b11000000
    };
    localparam [7:0] LUT_SW [2] = '{
        8'b00001111, 8'b11110000
    };
    localparam [7:0] LUT_SD = 8'b11111111;

    wire [7:0] mem_mask = {8{sb_op}} & LUT_SB[mem_addr[2:0]]
                        | {8{sh_op}} & LUT_SH[mem_addr[2:1]]
                        | {8{sw_op}} & LUT_SW[mem_addr[2:2]]
                        | {8{sd_op}} & LUT_SD;

    wire [63:0] store_data = {64{sb_op}} & ({8{mem_wdata[ 7:0]}})
                           | {64{sh_op}} & ({4{mem_wdata[15:0]}})
                           | {64{sw_op}} & ({2{mem_wdata[31:0]}})
                           | {64{sd_op}} & (mem_wdata) ;

    assign dbus_req     = mem_en & ex_valid & ma2ex_allow & (~cancel_memory);
    assign dbus_op      = mem_we;
    assign dbus_size    = mem_size;
    assign dbus_mask    = mem_mask;
    assign dbus_addr    = mem_addr;
    assign dbus_wdata   = store_data;
    assign dbus_uncache = ~(dbus_addr >= 32'h8000_0000 && dbus_addr < 32'h9000_0000);

/* ************************* MEM ************************* */

    // Difftest: skip_ref
    wire skip_ref = dbus_req & dbus_uncache;
endmodule
